Add "trivial-httpd" builtin, use it in tests
authorColin Walters <walters@verbum.org>
Fri, 5 Jul 2013 20:12:10 +0000 (16:12 -0400)
committerColin Walters <walters@verbum.org>
Fri, 5 Jul 2013 20:28:40 +0000 (16:28 -0400)
A simple HTTP server implementation is so few lines of code when one
is linking to libsoup anyways, so let's just have one here in ostree
that will be used for the test suite.

This allows us to run the archive tests that previously required
apache even in gnome-ostree.

Makefile-ostree.am
src/ostree/main.c
src/ostree/ot-builtin-trivial-httpd.c [new file with mode: 0644]
src/ostree/ot-builtins.h
tests/Makefile [deleted file]
tests/libtest.sh
tests/run-apache.c [deleted file]
tests/tmpdir-lifecycle.c [deleted file]

index 76cd04790b1a424fb71b22274b475145dff873c7..94d5d0888b6974f958309bb7a5d44071c576b6e1 100644 (file)
@@ -44,6 +44,7 @@ ostree_SOURCES = src/ostree/main.c \
        src/ostree/ot-builtin-remote.c \
        src/ostree/ot-builtin-rev-parse.c \
        src/ostree/ot-builtin-show.c \
+       src/ostree/ot-builtin-trivial-httpd.c \
        src/ostree/ot-builtin-write-refs.c \
        src/ostree/ot-main.h \
        src/ostree/ot-main.c \
index 2648ff490a0761c1f85b29d9b96e117b07e938af..da6db63843315ab61a03c9f6dd3c3529bdf1c926 100644 (file)
@@ -49,6 +49,7 @@ static OstreeCommand commands[] = {
   { "remote", ostree_builtin_remote, 0 },
   { "rev-parse", ostree_builtin_rev_parse, 0 },
   { "show", ostree_builtin_show, 0 },
+  { "trivial-httpd", ostree_builtin_trivial_httpd, OSTREE_BUILTIN_FLAG_NO_REPO },
   { "write-refs", ostree_builtin_write_refs, 0 },
   { NULL }
 };
diff --git a/src/ostree/ot-builtin-trivial-httpd.c b/src/ostree/ot-builtin-trivial-httpd.c
new file mode 100644 (file)
index 0000000..83484fc
--- /dev/null
@@ -0,0 +1,348 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <libsoup/soup.h>
+
+#include "ot-builtins.h"
+#include "ot-admin-builtins.h"
+#include "ot-admin-functions.h"
+#include "ot-main.h"
+#include "ostree.h"
+#include "ostree-repo-file.h"
+
+#include <glib/gi18n.h>
+
+static char *opt_port_file = NULL;
+static gboolean opt_daemonize;
+static gboolean opt_autoexit;
+
+typedef struct {
+  GFile *root;
+  gboolean running;
+} OtTrivialHttpd;
+
+static GOptionEntry options[] = {
+  { "daemonize", 'd', 0, G_OPTION_ARG_NONE, &opt_daemonize, "Fork into background when ready", NULL },
+  { "autoexit", 0, 0, G_OPTION_ARG_NONE, &opt_autoexit, "Automatically exit when directory is deleted", NULL },
+  { "port-file", 'p', 0, G_OPTION_ARG_FILENAME, &opt_port_file, "Write port number to PATH", "PATH" },
+  { NULL }
+};
+
+static int
+compare_strings (gconstpointer a, gconstpointer b)
+{
+  const char **sa = (const char **)a;
+  const char **sb = (const char **)b;
+
+  return strcmp (*sa, *sb);
+}
+
+static GString *
+get_directory_listing (const char *path)
+{
+  GPtrArray *entries;
+  GString *listing;
+  char *escaped;
+  DIR *dir;
+  struct dirent *dent;
+  int i;
+
+  entries = g_ptr_array_new ();
+  dir = opendir (path);
+  if (dir)
+    {
+      while ((dent = readdir (dir)))
+        {
+          if (!strcmp (dent->d_name, ".") ||
+              (!strcmp (dent->d_name, "..") &&
+               !strcmp (path, "./")))
+            continue;
+          escaped = g_markup_escape_text (dent->d_name, -1);
+          g_ptr_array_add (entries, escaped);
+        }
+      closedir (dir);
+    }
+
+  g_ptr_array_sort (entries, (GCompareFunc)compare_strings);
+
+  listing = g_string_new ("<html>\r\n");
+  escaped = g_markup_escape_text (strchr (path, '/'), -1);
+  g_string_append_printf (listing, "<head><title>Index of %s</title></head>\r\n", escaped);
+  g_string_append_printf (listing, "<body><h1>Index of %s</h1>\r\n<p>\r\n", escaped);
+  g_free (escaped);
+  for (i = 0; i < entries->len; i++)
+    {
+      g_string_append_printf (listing, "<a href=\"%s\">%s</a><br>\r\n",
+                              (char *)entries->pdata[i], 
+                              (char *)entries->pdata[i]);
+      g_free (entries->pdata[i]);
+    }
+  g_string_append (listing, "</body>\r\n</html>\r\n");
+
+  g_ptr_array_free (entries, TRUE);
+  return listing;
+}
+
+/* Only allow reading files that have o+r, and for directories, o+x.
+ * This makes this server relatively safe to use on multiuser
+ * machines.
+ */
+static gboolean
+is_safe_to_access (struct stat *stbuf)
+{
+  /* Only regular files or directores */
+  if (!(S_ISREG (stbuf->st_mode) || S_ISDIR (stbuf->st_mode)))
+    return FALSE;
+  /* Must be o+r */
+  if (!(stbuf->st_mode & S_IROTH))
+    return FALSE;
+  /* For directories, must be o+x */
+  if (S_ISDIR (stbuf->st_mode) && !(stbuf->st_mode & S_IXOTH))
+    return FALSE;
+  return TRUE;
+}
+
+static void
+do_get (OtTrivialHttpd  *self,
+        SoupServer      *server,
+        SoupMessage     *msg,
+        const char      *path)
+{
+  char *slash;
+  int ret;
+  struct stat stbuf;
+  gs_free char *safepath = NULL;
+
+  if (strstr (path, "../") != NULL)
+    {
+      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      goto out;
+    }
+
+  if (path[0] == '/')
+    path++;
+
+  safepath = g_build_filename (gs_file_get_path_cached (self->root), path, NULL);
+
+  do
+    ret = stat (safepath, &stbuf);
+  while (ret == -1 && errno == EINTR);
+  if (ret == -1)
+    {
+      if (errno == EPERM)
+        soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      else if (errno == ENOENT)
+        soup_message_set_status (msg, SOUP_STATUS_NOT_FOUND);
+      else
+        soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+      goto out;
+    }
+
+  if (!is_safe_to_access (&stbuf))
+    {
+      soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+      goto out;
+    }
+
+  if (S_ISDIR (stbuf.st_mode))
+    {
+      slash = strrchr (safepath, '/');
+      if (!slash || slash[1])
+        {
+          gs_free char *redir_uri = NULL;
+
+          redir_uri = g_strdup_printf ("%s/", soup_message_get_uri (msg)->path);
+          soup_message_set_redirect (msg, SOUP_STATUS_MOVED_PERMANENTLY,
+                                     redir_uri);
+        }
+      else
+        {
+          gs_free char *index_realpath = g_strconcat (safepath, "/index.html", NULL);
+          if (stat (index_realpath, &stbuf) != -1)
+            {
+              gs_free char *index_path = g_strconcat (path, "/index.html", NULL);
+              do_get (self, server, msg, index_path);
+            }
+          else
+            {
+              GString *listing = get_directory_listing (safepath);
+              soup_message_set_response (msg, "text/html",
+                                         SOUP_MEMORY_TAKE,
+                                         listing->str, listing->len);
+              g_string_free (listing, FALSE);
+            }
+        }
+    }
+  else 
+    {
+      if (!S_ISREG (stbuf.st_mode))
+        {
+          soup_message_set_status (msg, SOUP_STATUS_FORBIDDEN);
+          goto out;
+        }
+      
+      if (msg->method == SOUP_METHOD_GET)
+        {
+          GMappedFile *mapping;
+          SoupBuffer *buffer;
+
+          mapping = g_mapped_file_new (safepath, FALSE, NULL);
+          if (!mapping)
+            {
+              soup_message_set_status (msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+              goto out;
+            }
+
+          buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
+                                               g_mapped_file_get_length (mapping),
+                                               mapping, (GDestroyNotify)g_mapped_file_unref);
+          soup_message_body_append_buffer (msg->response_body, buffer);
+          soup_buffer_free (buffer);
+        }
+      else /* msg->method == SOUP_METHOD_HEAD */
+        {
+          gs_free char *length = NULL;
+
+          /* We could just use the same code for both GET and
+           * HEAD (soup-message-server-io.c will fix things up).
+           * But we'll optimize and avoid the extra I/O.
+           */
+          length = g_strdup_printf ("%lu", (gulong)stbuf.st_size);
+          soup_message_headers_append (msg->response_headers,
+                                       "Content-Length", length);
+        }
+      soup_message_set_status (msg, SOUP_STATUS_OK);
+    }
+ out:
+  return;
+}
+
+static void
+httpd_callback (SoupServer *server, SoupMessage *msg,
+                const char *path, GHashTable *query,
+                SoupClientContext *context, gpointer data)
+{
+  OtTrivialHttpd *self = data;
+  SoupMessageHeadersIter iter;
+
+  soup_message_headers_iter_init (&iter, msg->request_headers);
+
+  if (msg->method == SOUP_METHOD_GET || msg->method == SOUP_METHOD_HEAD)
+    do_get (self, server, msg, path);
+  else
+    soup_message_set_status (msg, SOUP_STATUS_NOT_IMPLEMENTED);
+}
+
+static void
+on_dir_changed (GFileMonitor  *mon,
+               GFile *file,
+               GFile *other,
+               GFileMonitorEvent  event,
+               gpointer user_data)
+{
+  OtTrivialHttpd *self = user_data;
+
+  if (event == G_FILE_MONITOR_EVENT_DELETED)
+    {
+      self->running = FALSE;
+      g_main_context_wakeup (NULL);
+    }
+}
+
+gboolean
+ostree_builtin_trivial_httpd (int argc, char **argv, GFile *repo_path, GError **error)
+{
+  gboolean ret = FALSE;
+  GCancellable *cancellable = NULL;
+  GOptionContext *context;
+  const char *dirpath;
+  OtTrivialHttpd appstruct;
+  OtTrivialHttpd *app = &appstruct;
+  gs_unref_object GFile *dir = NULL;
+  gs_unref_object SoupServer *server = NULL;
+  gs_unref_object GFileMonitor *dirmon = NULL;
+
+  memset (&appstruct, 0, sizeof (appstruct));
+
+  context = g_option_context_new ("[DIR] - Simple webserver");
+
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (argc > 1)
+    dirpath = argv[1];
+  else
+    dirpath = ".";
+
+  app->root = g_file_new_for_path (dirpath);
+
+  server = soup_server_new (SOUP_SERVER_PORT, 0,
+                            SOUP_SERVER_SERVER_HEADER, "ostree-httpd ",
+                            NULL);
+  soup_server_add_handler (server, NULL, httpd_callback, app, NULL);
+  if (opt_port_file)
+    {
+      gs_free char *portstr = g_strdup_printf ("%u\n", soup_server_get_port (server));
+      if (!g_file_set_contents (opt_port_file, portstr, strlen (portstr), error))
+        goto out;
+    }
+  soup_server_run_async (server);
+
+  if (opt_daemonize)
+    {
+      pid_t pid = fork();
+      if (pid == -1)
+        {
+          int errsv = errno;
+          g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errsv),
+                               g_strerror (errsv));
+          goto out;
+        }
+      else if (pid > 0)
+        {
+          /* Parent */
+          _exit (0);
+        }
+      /* Child, continue */
+    }
+
+  app->running = TRUE;
+  if (opt_autoexit)
+    {
+      dirmon = g_file_monitor_directory (app->root, 0, cancellable, error);
+      if (!dirmon)
+        goto out;
+      g_signal_connect (dirmon, "changed", G_CALLBACK (on_dir_changed), app);
+    }
+
+  while (app->running)
+    g_main_context_iteration (NULL, TRUE);
+  ret = TRUE;
+ out:
+  g_clear_object (&app->root);
+  if (context)
+    g_option_context_free (context);
+  return ret;
+}
index e8748640e685b532ed9c0770875a49792e654c2c..9847c5a4822e8505cf14dd3bf2888ee179347b1c 100644 (file)
@@ -46,6 +46,7 @@ gboolean ostree_builtin_show (int argc, char **argv, GFile *repo_path, GError **
 gboolean ostree_builtin_rev_parse (int argc, char **argv, GFile *repo_path, GError **error);
 gboolean ostree_builtin_remote (int argc, char **argv, GFile *repo_path, GError **error);
 gboolean ostree_builtin_write_refs (int argc, char **argv, GFile *repo_path, GError **error);
+gboolean ostree_builtin_trivial_httpd (int argc, char **argv, GFile *repo_path, GError **error);
 
 G_END_DECLS
 
diff --git a/tests/Makefile b/tests/Makefile
deleted file mode 100644 (file)
index 18d4d82..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-# Toplevel tests Makefile
-#
-# Copyright (C) 2011 Colin Walters <walters@verbum.org>
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the
-# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-# Boston, MA 02111-1307, USA.
-
-TESTS = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
-
-all: tmpdir-lifecycle run-apache
-
-tmpdir-lifecycle: tmpdir-lifecycle.c Makefile
-       gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
-
-run-apache: run-apache.c Makefile
-       gcc $(CFLAGS) `pkg-config --cflags --libs gio-unix-2.0` -o $@ $<
-
-check:
-       @for test in $(TESTS); do \
-         echo $$test; \
-         ./$$test; \
-       done
-
index 12ddd64602c4db5c9f0eb4c771bbdce3ed55e7b2..0da1564a785fc246518345f408cd12b92c8ac71f 100644 (file)
@@ -102,9 +102,6 @@ setup_test_repository () {
 }
 
 setup_fake_remote_repo1() {
-    if ! test -x ${SRCDIR}/run-apache; then
-       exit 77
-    fi
     mode=$1
     shift
     oldpwd=`pwd`
@@ -131,36 +128,10 @@ setup_fake_remote_repo1() {
     cd ${test_tmpdir}
     mkdir ${test_tmpdir}/httpd
     cd httpd
-    cat >httpd.conf <<EOF
-ServerRoot ${test_tmpdir}/httpd
-PidFile pid
-LogLevel crit
-ErrorLog log
-LockFile lock
-ServerName localhost
-
-LoadModule alias_module modules/mod_alias.so
-LoadModule cgi_module modules/mod_cgi.so
-LoadModule env_module modules/mod_env.so
-
-StartServers 1
-
-# SetEnv OSTREE_REPO_PREFIX ${test_tmpdir}/ostree-srv
-Alias /ostree/ ${test_tmpdir}/ostree-srv/
-# ScriptAlias /ostree/  ${test_tmpdir}/httpd/ostree-http-backend/
-EOF
-    ${SRCDIR}/tmpdir-lifecycle ${SRCDIR}/run-apache `pwd`/httpd.conf ${test_tmpdir}/httpd-address &
-    for i in $(seq 5); do
-       if ! test -f ${test_tmpdir}/httpd-address; then
-           sleep 1
-       else
-           break
-       fi
-    done
-    if ! test -f ${test_tmpdir}/httpd-address; then
-       echo "Error: timed out waiting for httpd-address file"
-       exit 1
-    fi
+    ln -s ${test_tmpdir}/ostree-srv ostree
+    ostree trivial-httpd --daemonize -p ${test_tmpdir}/httpd-port
+    port=$(cat ${test_tmpdir}/httpd-port)
+    echo "http://127.0.0.1:${port}" > ${test_tmpdir}/httpd-address
     cd ${oldpwd} 
 
     export OSTREE="ostree --repo=repo"
diff --git a/tests/run-apache.c b/tests/run-apache.c
deleted file mode 100644 (file)
index cabccb9..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include <gio/gio.h>
-#include <sys/types.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <sys/stat.h>
-
-/* Taken from gnome-user-share src/httpd.c under the GPLv2 */
-static int
-get_port (void)
-{
-  int sock;
-  int saved_errno;
-  struct sockaddr_in addr;
-  int reuse;
-  socklen_t len;
-
-  sock = socket (PF_INET, SOCK_STREAM, 0);
-  if (sock < 0)
-    {
-      return -1;
-    }
-  
-  memset (&addr, 0, sizeof (addr));
-  addr.sin_family = AF_INET;
-  addr.sin_port = 0;
-  addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
-
-  reuse = 1;
-  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof (reuse));
-  if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == -1)
-    {
-      saved_errno = errno;
-      close (sock);
-      errno = saved_errno;
-      return -1;
-    }
-
-  len = sizeof (addr);
-  if (getsockname (sock, (struct sockaddr *)&addr, &len) == -1)
-    {
-      saved_errno = errno;
-      close (sock);
-      errno = saved_errno;
-      return -1;
-    }
-
-#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
-  /* XXX This exposes a potential race condition, but without this,
-   * httpd will not start on the above listed platforms due to the fact
-   * that SO_REUSEADDR is also needed when Apache binds to the listening
-   * socket.  At this time, Apache does not support that socket option.
-   */
-  close (sock);
-#endif
-  return ntohs (addr.sin_port);
-}
-
-static const char *known_httpd_modules_locations [] = {
-  "/usr/libexec/apache2",
-  "/usr/lib/apache2/modules",
-  "/usr/lib64/httpd/modules",
-  "/usr/lib/httpd/modules",
-  NULL
-};
-
-static gchar*
-get_httpd_modules_path ()
-{
-  int i;
-
-  for (i = 0; known_httpd_modules_locations[i]; i++)
-    {
-      if (g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_EXECUTABLE)
-         && g_file_test (known_httpd_modules_locations[i], G_FILE_TEST_IS_DIR))
-       {
-         return g_strdup (known_httpd_modules_locations[i]);
-       }
-    }
-  return NULL;
-}
-
-int
-main (int     argc,
-      char  **argv)
-{
-  int port;
-  char *listen;
-  char *address_string;
-  GError *error = NULL;
-  GPtrArray *httpd_argv;
-  char *modules;
-
-  if (argc != 3)
-    {
-      fprintf (stderr, "usage: run-apache CONF PORTFILE");
-      return 1;
-    }
-
-  g_type_init ();
-
-  port = get_port ();
-  if (port == -1)
-    {
-      perror ("Failed to bind port");
-      return 1;
-    }
-
-  httpd_argv = g_ptr_array_new ();
-  g_ptr_array_add (httpd_argv, "httpd");
-  g_ptr_array_add (httpd_argv, "-DFOREGROUND");
-  g_ptr_array_add (httpd_argv, "-f");
-  g_ptr_array_add (httpd_argv, argv[1]);
-  g_ptr_array_add (httpd_argv, "-C");
-  listen = g_strdup_printf ("Listen 127.0.0.1:%d", port);
-  g_ptr_array_add (httpd_argv, listen);
-  g_ptr_array_add (httpd_argv, NULL);
-
-  address_string = g_strdup_printf ("http://127.0.0.1:%d\n", port);
-  
-  if (!g_file_set_contents (argv[2], address_string, -1, &error))
-    {
-      g_printerr ("%s\n", error->message);
-      return 1;
-    }
-
-  setenv ("LANG", "C", 1);
-  modules = get_httpd_modules_path ();
-  if (modules == NULL)
-    {
-      g_printerr ("Failed to find httpd modules\n");
-      return 1;
-    }
-  if (symlink (modules, "modules") < 0)
-    {
-      perror ("failed to make modules symlink");
-      return 1;
-    }
-  execvp ("httpd", (char**)httpd_argv->pdata);
-  perror ("Failed to run httpd");
-  return 1;
-}
diff --git a/tests/tmpdir-lifecycle.c b/tests/tmpdir-lifecycle.c
deleted file mode 100644 (file)
index 7345df1..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
- *
- * Kill a child process when the current directory is deleted
- *
- * Copyright (C) 2011 Colin Walters <walters@verbum.org>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
- *
- * Author: Colin Walters <walters@verbum.org>
- */
-
-#include <gio/gio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-
-struct TmpdirLifecyleData {
-  GMainLoop *loop;
-  GPid pid;
-  gboolean exited;
-};
-
-static void
-on_dir_changed (GFileMonitor  *mon,
-               GFile *file,
-               GFile *other,
-               GFileMonitorEvent  event,
-               gpointer user_data)
-{
-  struct TmpdirLifecyleData *data = user_data;
-
-  if (event == G_FILE_MONITOR_EVENT_DELETED)
-    g_main_loop_quit (data->loop);
-}
-
-static void
-on_child_exited (GPid  pid,
-                 int status,
-                 gpointer user_data)
-{
-  struct TmpdirLifecyleData *data = user_data;
-
-  data->exited = TRUE;
-  g_main_loop_quit (data->loop);
-}
-
-int
-main (int     argc,
-      char  **argv)
-{
-  GFileMonitor *monitor;
-  GFile *curdir;
-  GError *error = NULL;
-  GPtrArray *new_argv;
-  int i;
-  struct TmpdirLifecyleData data;
-
-  g_type_init ();
-
-  memset (&data, 0, sizeof (data));
-
-  data.loop = g_main_loop_new (NULL, TRUE);
-
-  curdir = g_file_new_for_path (".");
-  monitor = g_file_monitor_directory (curdir, 0, NULL, &error);
-  if (!monitor)
-    exit (1);
-  g_signal_connect (monitor, "changed",
-                   G_CALLBACK (on_dir_changed), &data);
-
-  new_argv = g_ptr_array_new ();
-  for (i = 1; i < argc; i++)
-    g_ptr_array_add (new_argv, argv[i]);
-  g_ptr_array_add (new_argv, NULL);
-
-  if (!g_spawn_async (NULL, (char**)new_argv->pdata, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
-                      NULL, NULL, &data.pid, &error))
-    {
-      g_printerr ("%s\n", error->message);
-      return 1;
-    }
-  g_child_watch_add (data.pid, on_child_exited, &data);
-
-  g_main_loop_run (data.loop);
-
-  if (!data.exited)
-    kill (data.pid, SIGTERM);
-
-  return 0;
-}